#ifndef __REF_H__
#define __REF_H__

#include "gofgame/common/IllegalArgumentException.h"

namespace gofgame {
	namespace common {

		template <typename T>
		struct Ref {
		public:
			Ref() {
				this->ptr = nullptr;
			}

			Ref(T *ptr) {
				this->ptr = ptr;
				if (ptr) {
					ptr->retain();
				}
			}

			Ref(const Ref<T> &ref) {
				this->ptr = ref.ptr;
				if (ref.ptr) {
					ptr->retain();
				}
			}

			template <typename X> Ref(X *ptr) {
				T *ptr_ = dynamic_cast<T*>(ptr);
				this->ptr = ptr_;
				if (ptr_) {
					ptr_->retain();
				}
			}

			~Ref() {
				this->release();
			}

			Ref<T> &initialize() {
				if (ptr) {
					ptr->release();
				}
				this->ptr = new T(); //Without retain()
				return *this;
			}

			Ref<T> &initialize(T *ptr) {
				if (this->ptr) {
					this->ptr->release();
				}
				this->ptr = ptr; //Without retain()
				return *this;
			}

			operator T*() const {
				return this->ptr;
			}

			template <typename X> operator Ref<X>() const {
				X *ptr_ = dynamic_cast<X*>(this->ptr);
				return Ref<X>(ptr_);
			}

			operator bool() const {
				return this->ptr != nullptr;
			}

			bool operator !() const {
				return this->ptr == nullptr;
			}

			T *operator ->() const {
				return this->ptr;
			}

			Ref<T> &operator = (T *ptr) {
				if (this->ptr != ptr) {
					if (this->ptr) {
						this->ptr->release();
					}
					this->ptr = ptr;
					if (ptr) {
						ptr->retain();
					}
				}
				return *this;
			}

			Ref<T>& operator = (const Ref<T> &ref) {
				if (this != &ref && this->ptr != ref.ptr) {
					if (ref.ptr) {
						ref.ptr->retain();
					}
					if (this->ptr) {
						this->ptr->release();
					}
					this->ptr = ref.ptr;
				}
				return *this;
			}

			void release() {
				T *ptr = this->ptr;
				if (ptr) {
					this->ptr = nullptr;
					ptr->release();
				}
			}

			T *autorelase() {
				T *ptr = this->ptr;
				if (ptr) {
					this->ptr = nullptr;
					ptr->autorelease();
				}
				return ptr;
			}

			T *unwrap() const {
				return this->ptr;
			}
		private:
			T *ptr;
		};
	}
}

template <typename T> bool operator == (const gofgame::common::Ref<T> &ref1, const gofgame::common::Ref<T> &ref2) {
	return ref1.operator T *() == ref2.operator T *();
}

template <typename T> bool operator != (const gofgame::common::Ref<T> &ref1, const gofgame::common::Ref<T> &ref2) {
	return ref1.operator T *() != ref2.operator T *();
}

template <typename T> bool operator == (const gofgame::common::Ref<T> &ref, T *p) {
	return ref.operator T *() == p;
}

template <typename T> bool operator != (const gofgame::common::Ref<T> &ref, T *p) {
	return ref.operator T *() != p;
}

template <typename T> bool operator == (T *p, const gofgame::common::Ref<T> &ref) {
	return p == ref.operator T *();
}

template <typename T> bool operator != (T *p, const gofgame::common::Ref<T> &ref) {
	return p != ref.operator T *();
}

#endif //__REF_H__
